/**
 * Copyright (c) 2011-2023, James Zhan 詹波 (jfinal@126.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.jse.tpl;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.jse.Jse;
import com.jse.Lang;

/**
 * Engine
 *
 * Example：
 * Engine.use().getTemplate(fileName).render(...);
 * Engine.use().getTemplate(fileName).renderToString(...);
 */
public class Tpl {

	public static final String MAIN_ENGINE_NAME = "main";

	private static Tpl MAIN_ENGINE=new Tpl(MAIN_ENGINE_NAME);
	private static Map<String, Tpl> engineMap = new HashMap<>(64, 0.5F){
		{
			put(MAIN_ENGINE_NAME,MAIN_ENGINE);
		}
	};

	private String name;
	private boolean cacheStringTemplate = false;
	private EngineConfig config = new EngineConfig();

	private Map<String, Template> templateCache = new ConcurrentHashMap<String, Template>(2048, 0.5F);

	/**
	 * Create engine without management of JFinal
	 */
	public Tpl() {
		this.name = "NO_NAME";
	}

	/**
	 * Create engine by engineName without management of JFinal
	 */
	public Tpl(String engineName) {
		this.name = engineName;
	}

	/**
	 * Using the main Engine
	 */
	public static Tpl use() {
		return MAIN_ENGINE;
	}

	/**
	 * Using the engine with engine name
	 */
	public static Tpl use(String engineName) {
		return engineMap.get(engineName);
	}

	/**
	 * Get template by file name
	 */
	public Template getTemplate(String fileName) {
		return getTemplate(null, fileName);
	}
	
	public Template getTemplate(String parent,String fileName) {
		Template template = templateCache.get(fileName);
		if (template == null||(Jse.dev&&template.isModified())) {
			Source source = new Source(parent, fileName,Jse.encoding);
			Env env = new Env(config);
			Parser parser = new Parser(env, source.getContent(), fileName);
			if (Jse.dev) {
				env.addSource(source);
			}
			Stat stat = parser.parse();
			template = new Template(env, stat);
			templateCache.put(fileName, template);
		} 
		return template;
	}

	/**
	 * Get template by string content and do not cache the template
	 */
	public Template getTemplateByString(String content) {
		return getTemplateByString(content, cacheStringTemplate);
	}

	/**
	 * Get template by string content
	 *
	 * 重要：StringSource 中的 cacheKey = HashKit.md5(content)，也即 cacheKey
	 *     与 content 有紧密的对应关系，当 content 发生变化时 cacheKey 值也相应变化
	 *     因此，原先 cacheKey 所对应的 Template 缓存对象已无法被获取，当 getTemplateByString(String)
	 *     的 String 参数的数量不确定时会引发内存泄漏
	 *
	 *     当 getTemplateByString(String, boolean) 中的 String 参数的
	 *     数量可控并且确定时，才可对其使用缓存
	 *
	 * @param content 模板内容
	 * @param cache true 则缓存 Template，否则不缓存
	 */
	public Template getTemplateByString(String content, boolean cache) {
		if (!cache) {
			return getTemplate(new Source(content, cache));
		}
		String cacheKey = Lang.md5(content);
		return getTemplateByString(content,cacheKey);
	}

	public Template getTemplateByString(String content, String cacheKey) {
		return getTemplate(new Source(content, cacheKey));
	}

	/**
	 * Get template by implementation of ISource
	 */
	public Template getTemplate(Source source) {
		String cacheKey = source.getCacheKey();
		Template template = templateCache.get(cacheKey==null?"":cacheKey);
		if (cacheKey == null||template == null||(Jse.dev&&template.isModified())) {
			Env env = new Env(config);
			Parser parser = new Parser(env, source.getContent(), null);
			if (Jse.dev) {
				env.addSource(source);
			}
			Stat stat = parser.parse();
			template = new Template(env, stat);
			templateCache.put(cacheKey==null?"":cacheKey, template);
		} 
		return template;
	}

	/**
	 * Add shared function by file
	 */
	public Tpl addSharedFunction(String fileName) {
		config.addSharedFunction(fileName);
		return this;
	}

	/**
	 * Add shared function by files
	 */
	public Tpl addSharedFunction(String... fileNames) {
		config.addSharedFunction(fileNames);
		return this;
	}

	/**
	 * Add shared object
	 */
	public Tpl addSharedObject(String name, Object object) {
		config.addSharedObject(name, object);
		return this;
	}

	public Tpl removeSharedObject(String name) {
		config.removeSharedObject(name);
		return this;
	}

	/**
	 * Add shared method from object
	 */
	public Tpl addSharedMethod(Object sharedMethodFromObject) {
		config.addSharedMethod(sharedMethodFromObject);
		return this;
	}

	/**
	 * Add shared method from class
	 */
	public Tpl addSharedMethod(Class<?> sharedMethodFromClass) {
		config.addSharedMethod(sharedMethodFromClass);
		return this;
	}

	/**
	 * Add shared static method of Class
	 */
	public Tpl addSharedStaticMethod(Class<?> sharedStaticMethodFromClass) {
		config.addSharedStaticMethod(sharedStaticMethodFromClass);
		return this;
	}

	/**
	 * Remove shared Method by method name
	 */
	public Tpl removeSharedMethod(String methodName) {
		config.removeSharedMethod(methodName);
		return this;
	}

	/**
	 * Remove shared Method of the Class
	 */
	public Tpl removeSharedMethod(Class<?> clazz) {
		config.removeSharedMethod(clazz);
		return this;
	}

	/**
	 * Remove shared Method
	 */
	public Tpl removeSharedMethod(Method method) {
		config.removeSharedMethod(method);
		return this;
	}

	/**
	 * Remove shared Method
	 */
	public Tpl removeSharedMethod(String methodName, Class<?>... paraTypes) {
		config.removeSharedMethod(methodName, paraTypes);
		return this;
	}

	/**
	 * Remove template cache by cache key
	 */
	public void removeTemplateCache(String cacheKey) {
		templateCache.remove(cacheKey);
	}

	/**
	 * Remove all template cache
	 */
	public void removeAllTemplateCache() {
		templateCache.clear();
	}

	public int getTemplateCacheSize() {
		return templateCache.size();
	}

	public String getName() {
		return name;
	}

	public String toString() {
		return "Template Engine: " + name;
	}

	// Engine config below ---------

	public EngineConfig  getEngineConfig() {
		return config;
	}

	/**
	 * 配置是否缓存字符串模板，也即是否缓存通过 getTemplateByString(String content)
	 * 方法获取的模板，默认配置为 false
	 */
	public Tpl setCacheStringTemplate(boolean cacheStringTemplate) {
		this.cacheStringTemplate = cacheStringTemplate;
		return this;
	}


	/**
	 * Engine 独立设置为 devMode 可以方便模板文件在修改后立即生效，
	 * 但如果在 devMode 之下并不希望对 addSharedFunction(...)，
	 * 添加的模板进行是否被修改的检测可以通过此方法设置 false 参进去
	 *
	 * 注意：Engine 在生产环境下(devMode 为 false)，该参数无效
	 */
	public Tpl setReloadModifiedSharedFunctionInDevMode(boolean reloadModifiedSharedFunctionInDevMode) {
		config.setReloadModifiedSharedFunctionInDevMode(reloadModifiedSharedFunctionInDevMode);
		return this;
	}

	public static void addExtensionMethod(Class<?> targetClass, Object objectOfExtensionClass) {
		MethodKit.addExtensionMethod(targetClass, objectOfExtensionClass);
	}

	public static void addExtensionMethod(Class<?> targetClass, Class<?> extensionClass) {
		MethodKit.addExtensionMethod(targetClass, extensionClass);
	}

	public static void removeExtensionMethod(Class<?> targetClass, Object objectOfExtensionClass) {
		MethodKit.removeExtensionMethod(targetClass, objectOfExtensionClass);
	}

	public static void removeExtensionMethod(Class<?> targetClass, Class<?> extensionClass) {
		MethodKit.removeExtensionMethod(targetClass, extensionClass);
	}

	/**
	 * 添加 FieldGetter 实现类到指定的位置
	 *
	 * 系统当前默认 FieldGetter 实现类及其位置如下：
	 * GetterMethodFieldGetter  ---> 调用 getter 方法取值
	 * RealFieldGetter			---> 直接获取 public 型的 object.field 值
	 * ModelFieldGetter			---> 调用 Model.get(String) 方法取值
	 * RecordFieldGetter			---> 调用 Record.get(String) 方法取值
	 * MapFieldGetter			---> 调用 Map.get(String) 方法取值
	 * ArrayLengthGetter			---> 获取数组长度
	 *
	 * 根据以上次序，如果要插入 IsMethodFieldGetter 到 GetterMethodFieldGetter
	 * 之后的代码如下：
	 * Engine.addFieldGetter(1, new IsMethodFieldGetter());
	 *
	 * 注：IsMethodFieldGetter 系统已经提供，只是默认没有启用。该实现类通过调用
	 *    target.isXxx() 方法获取 target.xxx 表达式的值，其中 isXxx() 返回值
	 *    必须是 Boolean/boolean 类型才会被调用
	 */
//	public static void addFieldGetter(int index, FieldGetter fieldGetter) {
//		FieldKit.addFieldGetter(index, fieldGetter);
//	}
	
	/**
     * 设置为 true 支持静态方法调用表达式，自 jfinal 5.0.2 版本开始默认值为 false
     */
    public Tpl setStaticMethodExpression(boolean enable) {
        config.setStaticMethodExpression(enable);
        return this;
    }
    
    /**
     * 设置为 true 支持静态属性访问表达式，自 jfinal 5.0.2 版本开始默认值为 false
     */
    public Tpl setStaticFieldExpression(boolean enable) {
        config.setStaticFieldExpression(enable);
        return this;
    }
}





